#ifndef PW_PBL_SCHEMA_HPP
#define PW_PBL_SCHEMA_HPP

#include "Allocator.hpp"
#include "NamedTuple.hpp"
#include "Concepts/Core.hpp"

namespace pw_pbl::Schema
{
	template <typename SCHEMA, Meta::Auto... V>
	struct ShareSchema;

	template <typename SCHEMA, Meta::Auto... V> requires requires { SCHEMA(V()...); }
	struct ShareSchema<SCHEMA, V...>
	{
		static constexpr SCHEMA ESchema = SCHEMA(V()...);
	};

	template <typename SCHEMA, Meta::Auto... V> requires std::is_aggregate_v<SCHEMA> && (!requires { SCHEMA(V()...); })
	struct ShareSchema<SCHEMA, V...>
	{
		static constexpr SCHEMA ESchema{V()...};
	};

	template <typename T>
	inline constexpr bool EIsVLArray = false;

	template <typename T>
	inline constexpr bool EIsVLArray<T[]> = true;

	template <typename T, size_t MaxN>
	inline constexpr bool EIsVLArray<T[0][MaxN]> = true;

	template <typename T>
	struct ElementOf
	{
		using type = T;
	};
	template <typename T>
	struct ElementOf<T[]>
	{
		using type = T;
	};
	template <typename T, size_t N>
	struct ElementOf<T[N]>
	{
		using type = T;
	};
	template <typename T, size_t MaxN>
	struct ElementOf<T[0][MaxN]>
	{
		using type = T;
	};

	struct Null
	{};

	template <template <typename...> class TRAITS>
	struct Imp;

	template <typename T, template <typename...> class TRAITS>
	concept ccField = requires(T &obj) { typename Imp<TRAITS>::Field{obj}; };

	template <template <typename...> class TRAITS>
	struct Imp
	{
		template <typename T>
		struct DefaultTraits;

		template <typename T>
		struct Traits : public std::conditional_t<ccCompleteType<TRAITS<T>>, TRAITS<T>, DefaultTraits<T>>
		{
			using Data = typename std::conditional_t<requires { typename TRAITS<T>::Data; }, TRAITS<T>, DefaultTraits<T>>::Data;
			using ExtraData = typename std::conditional_t<requires { typename TRAITS<T>::ExtraData; }, TRAITS<T>, DefaultTraits<T>>::ExtraData;
			using Schema = typename std::conditional_t<requires { typename TRAITS<T>::Schema; }, TRAITS<T>, DefaultTraits<T>>::Schema;
		};

		template <typename T>
		using TraitsData = typename Traits<T>::Data;

		template <typename T>
		using TraitsExtraData = typename Traits<T>::ExtraData;

		template <typename T>
		using TraitsSchema = typename Traits<T>::Schema;

		template <typename T>
		struct FullRef;

		template <typename T>
		struct FullData;

		template <typename T, Meta::Auto... V>
		struct RawField : public ShareSchema<TraitsSchema<T>, V...>
		{
			using type = T;
			using ShareSchema<TraitsSchema<T>, V...>::ESchema;
			static constexpr bool EIsVLArray = ::pw_pbl::Schema::EIsVLArray<T>;
		};

		template <Meta::Str NAME, typename T, Meta::Auto... V>
		struct Field : public RawField<T, V...>
		{
			using type = T;
			using RawField<T, V...>::ESchema;
			using RawField<T, V...>::EIsVLArray;
			using metaType = Meta::Type<T, NAME>;
			using DataType = Meta::Type<TraitsData<T>, NAME>;
			using ExtraDataType = Meta::Type<TraitsExtraData<T>, NAME>;
			using SchemaType = Meta::Type<TraitsSchema<T>, NAME>;
		};

		template <Meta::Str NAME, typename T, Meta::Auto... V>
		struct Field<NAME, RawField<T, V...>> : public Field<NAME, T, V...>
		{};

		template <ccField<TRAITS>... FLD>
		struct Struct
		{
			using DefaultTraits = DefaultTraits<Struct>;
			using Traits = Traits<Struct>;
			using Data = TraitsData<Struct>;
			using ExtraData = TraitsExtraData<Struct>;
			using Schema = TraitsSchema<Struct>;
			using FullData = FullData<Struct>;
			using FullCRef = FullRef<const Struct>;
			using FullRef = FullRef<Struct>;
		};

		template <ccField<TRAITS>... FLD>
		struct DefaultTraits<Struct<FLD...>>
		{
			using Data = pw_pbl::NamedTuple<typename FLD::DataType...>;
			using ExtraData = pw_pbl::NamedTuple<typename FLD::ExtraDataType...>;

			struct Schema
			{
				const pw_pbl::NamedTuple<typename FLD::SchemaType...> &field = ShareSchema<pw_pbl::NamedTuple<typename FLD::SchemaType...>, FLD::ESchema...>::ESchema;
				constexpr Schema() noexcept = default;

				template <size_t I>
				constexpr decltype(auto) get() const noexcept { return field.template get<I>(); }
			};
			static constexpr Schema ESchema = {};
		};

		template <typename T, size_t N>
		struct DefaultTraits<T[N]>
		{
			using Data = std::array<TraitsData<T>, N>;
			using ExtraData = std::array<TraitsExtraData<T>, N>;
			using Schema = TraitsSchema<T>;
		};

		template <typename T>
		struct DefaultTraits<T[]>
		{
			using Data = std::vector<TraitsData<T>, StdAllocator<TraitsData<T>>>;
			using ExtraData = std::vector<TraitsExtraData<T>, StdAllocator<TraitsExtraData<T>>>;
			using Schema = TraitsSchema<T>;
		};

		template <typename T, size_t MaxN>
		struct DefaultTraits<T[0][MaxN]>
		{
			using Data = std::array<TraitsData<T>, MaxN>;
			struct ExtraData
			{
				size_t size;
				std::array<TraitsExtraData<T>, MaxN> data;
			};
			using Schema = TraitsSchema<T>;
		};

		template <typename T>
		struct DefaultTraits
		{
			using Data = T;
			using ExtraData = Null;
			using Schema = Null;
		};

		template <typename T>
		struct FullRef
		{
			using type = std::remove_const_t<T>;

		private:
			using element = typename ElementOf<type>::type;
			template <typename OTH>
			using const_like = std::conditional_t<std::is_const_v<T>, const OTH, OTH>;

		public:
			const_like<TraitsData<type>> &data;
			const_like<TraitsExtraData<type>> &extraData;
			const TraitsSchema<type> &schema;

			constexpr FullRef<const_like<element>> operator[](size_t i) const noexcept requires(!std::same_as<element, type>)
			{
				return {.data = data[i], .extraData = extraData[i], .schema = schema};
			}

			template <typename OTH>
			constexpr operator FullRef<OTH>() const noexcept requires requires { FullRef<OTH>{.data = data, .extraData = extraData, .schema = schema}; }
			{
				return {.data = data, .extraData = extraData, .schema = schema};
			}
		};

	private:
		template <bool IsConst, ccField<TRAITS>... FLD>
		struct StructRef
		{
			template <typename T>
			using FullRef = FullRef<std::conditional_t<IsConst, const T, T>>;

			using Self = FullRef<Struct<FLD...>>;

			template <typename F>
			static constexpr auto _visit = []<size_t... IDS>(std::integer_sequence<size_t, IDS...>) -> std::array<std::common_type_t<std::invoke_result_t<F, FullRef<typename FLD::type>>...> (*)(const Self &, F &), sizeof...(FLD)>
			{
				return {[](const Self &self, F &oper) { return oper(FullRef<typename FLD::type>{.data = self.data.template get<IDS>(), .extraData = self.extraData.template get<IDS>(), .schema = self.schema.template get<IDS>()}); }...};
			}
			(std::make_index_sequence<sizeof...(FLD)>{});

			template <typename F>
			static constexpr decltype(auto) visit(const Self &self, F &&oper) noexcept
			{
				for (auto ff : _visit<std::remove_reference_t<F>>)
					ff(self, oper);
			}

			template <typename F>
			static constexpr decltype(auto) visit(const Self &self, size_t i, F &&oper) noexcept
			{
				assert(i < sizeof...(FLD));
				return _visit<std::remove_reference_t<F>>[i](self, oper);
			}
		};

	public:
		template <ccField<TRAITS>... FLD>
		struct FullRef<Struct<FLD...>>
		{
			TraitsData<Struct<FLD...>> &data;
			TraitsExtraData<Struct<FLD...>> &extraData;
			const TraitsSchema<Struct<FLD...>> &schema;

			template <typename... T>
			constexpr decltype(auto) visit(T &&...p) const noexcept { return StructRef<false, FLD...>::visit(*this, std::forward<T>(p)...); }
		};

		template <ccField<TRAITS>... FLD>
		struct FullRef<const Struct<FLD...>>
		{
			const TraitsData<Struct<FLD...>> &data;
			const TraitsExtraData<Struct<FLD...>> &extraData;
			const TraitsSchema<Struct<FLD...>> &schema;

			template <typename... T>
			constexpr decltype(auto) visit(T &&...p) const noexcept { return StructRef<true, FLD...>::visit(*this, std::forward<T>(p)...); }
		};

		template <typename T>
		struct FullData
		{
			TraitsData<T> data;
			TraitsExtraData<T> extraData;
			const TraitsSchema<T> &schema;

			constexpr operator FullRef<T>() noexcept { return {.data = data, .extraData = extraData, .schema = schema}; }
			constexpr operator FullRef<const T>() const noexcept { return {.data = data, .extraData = extraData, .schema = schema}; }
		};
	};

} // namespace pw_pbl::Schema
#endif // PW_PBL_SCHEMA_HPP
